ASP.NET Core 2.0入门

代码先行

作者:陈广 日期:2018-7-9


之前已经讲解如何使用 PostgreSQL 创建数据库和数据表。接下来讲讲如何使用 .NET Core 控制数据库。在 .NET Core 中,一般通过 EF Core(Entity Framework Core)来使用数据库。EF Core 译为实体框架,早期的 C# 连接数据库是通过代码直接连接并控制,之后出现分层思想,对于数据库中的每个表在 C# 中都创建一个实体类与之对应,以操作实体类的方式来代替之前的直接操作数据库。这好比你买矿泉水,以前直接找厂家拿货,之后厂家只出货给各地经销商,你只能去经销商那去拿货了。EF Core 就是这个经销商。这样做的好处嘛!原来你喝娃哈哈,现在想换口味,喝农夫山泉,让经销商想办法去进货就行了,你不需要做任何改变,以前怎么买水,现在还是怎么买水。怎么联系农夫山泉厂家变成了经销商的事。也就是说你的程序以前使用的是 Oracle 数据库,由于公司没钱,希望转用免费数据库 PostgreSQL,只需付很小的代价更改程序就可以完成这个转变了。

基于 EF Core 的开发有代码先行(Code Firse)和数据库先行(Database First)两种模式。代码先行意味着先使用 C# 代码建立数据库实体类,然后 .NET Core 会通过这些实体类自动生成相应的数据库。数据库先行则是先建立数据库,然后 .NET Core 根据数据库自动生成相应的 C# 实体类。本文先讲述如何使用代码先行,使用的数据库结构依照之前 PostgreSQL 中的学生信息数据库。

创建项目

新建一个名为StuInfoSys的 ASP.NET Core 应用程序,选择Web 应用程序(模型视图控制器)。按【确定】按钮后创建项目。简而言之就是创建一个 MVC 项目,请参照之前的文章,这里不再详述。

创建实体类

之前讲解 PostgreSQL 时我们设计的学生信息数据库有三个表:系部表、班级表、学生表。现在我们建立针对就三张表的实体类。

系部表实体类

我们先来看看系部表结构:

CREATE TABLE depment(
    depId INT PRIMARY KEY,
    depName CHAR(30)
);

在 【Models】 文件夹下添加一个 Depment 类,填入如下代码:

namespace StuInfoSys.Models
{
    public class Depment
    {
        public int DepId { get; set; }
        public string DepName { get; set; }
        public ICollection<Class> Classes { get; set; }
    }
}

Depment类中,DepId属性对应的是系部表中的depId字段;DepName属性则对应depName字段;还有一个Classes属性则用于表示系部表和班级表的关系(主外键关系)。这里需要注意的是系部表的depId为主键,班级表的depId为外键。主外键关系为一对多关系,系部表为 1,班级表为多,表示一个系部对应多个班级。Classes属性就是用于表示此系部下的多个班级的。

班级表实体类

班级表结构:

CREATE TABLE class(
    classId INT PRIMARY KEY,
    className VARCHAR(50) NOT NULL,
    depId INT REFERENCES depment(depId)
);

在 【Models】 文件夹下添加一个 Class 类,填入如下代码:

namespace StuInfoSys.Models
{
    public class Class
    {
        public int ClassID { get; set; }
        public string ClassName { get; set; }
        public int DepId { get; set; }
        public ICollection<Student> Students { get; set; }
    }
}

Students属性表示此班级下的多个学生。

学生表实体类

学生表结构:

CREATE TABLE student(
    stuId INT PRIMARY KEY,
    stuName CHAR(8) NOT NULL,
    sex CHAR(2),
    birthday DATE,
    classId INT REFERENCES class(classId)
);

在 【Models】 文件夹下添加一个 Student 类,填入如下代码:

namespace StuInfoSys.Models
{
    public class Student
    {
        public int StuId { get; set; }
        public string StuName { get; set; }
        public string Sex { get; set; }
        public DateTime Birthday { get; set; }
        public int ClassId { get; set; }
    }
}

通过上面实体类的建立,我们知道,类中的每个属性都跟数据表中的字段一一对应。如果有主外键连接,则在主键所在表需要创建相应的集合类以表示外键表中的多个数据。

创建数据库上下文类

Context(译为上下文),这个翻译词非常拗口,之前在单片机操作系统里见过这个词,这里又出现,我始终无法完全理解它的含义,原因大概就是翻译不好吧。不管了,不理解不代表不会用。

要将实体类和数据库中的表关联,需要创建一数据库上下文类。我们将此类放于一个单独的文件夹内。

创建

在项目下新建一个 EntityFramework 文件夹,并在此文件夹下新建一个 StuinfoDbContext类。创建完成后项目文件结构如下图所示: StuinfoDbContext类的代码更改如下:

using Microsoft.EntityFrameworkCore;
using StuInfoSys.Models;

namespace StuInfoSys.EntityFramework
{
    public class StuinfoDbContext: DbContext
    {
        public StuinfoDbContext(DbContextOptions<StuinfoDbContext> options) : base(options)
        {
            //构造函数
        }
        //对应三个实体类的 DbSet 集合属性
        public DbSet<Depment> Depments { get; set; }
        public DbSet<Class> Classes { get; set; }
        public DbSet<Student> Students { get; set; }

        //更改表名并设置主键
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Depment>().ToTable("Depment").HasKey(a => a.DepId);
            modelBuilder.Entity<Class>().ToTable("Class").HasKey(a => a.ClassID);
            modelBuilder.Entity<Student>().ToTable("Student").HasKey(a => a.StuId);
        }
    }
}

OnModelCreating方法中的ToTable方法用于更改数据表名称,如果不使用此方法则创建的表名为对应的属性名称。如系部表DbSet属性名为Depments,则创建的数据表名也为Depments。我个人不喜欢用复数形式命名数据表,所以需要手动更改表名。HasKey方法用于指定数据表的主键。

另外需要注意,创建数据库上下文类需要继承自DbContext类。

通过依赖注入注册数据库上下文

如果要使用数据库上下文服务,就需要通过依赖注入进行注册。

打开【startup.cs】文件,引入如下两个命名空间:

using StuInfoSys.EntityFramework;
using Microsoft.EntityFrameworkCore;

更改里面的ConfigureServices方法如下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<StuinfoDbContext>(d => d.UseSqlServer(Configuration.
        GetConnectionString("Default")));
    services.AddMvc();
}

我们通过使用services.AddDbContext方法,注册数据库上下文服务,里面指定了使用 SqlServer 数据库,并通过数据库连接字符串来指定所使用的数据库。连接字符串需要到配置文件中读取,所以接下来要更改配置文件以加入数据库连接字符串。

配置数据库连接字符串

打开【appsettings.json】文件,在其中加入ConnectionStrings项,最后文件代码如下:

{
  "ConnectionStrings": {
    "Default": "Server=(localdb)\\mssqllocaldb; Database=StuinfoDB; AttachDbFilename=D:\\projects\\Stuinfo_DB.mdf;Integrated Security=True;"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

为演示方便,这里并没有使用 SQL Server,而是使用 SQL Server 的本地数据库版本 LocalDB。不用安装 SQL Server,当然很爽。需要注意的是连接字符串中,AttachDbFilename项指定了数据库文件所在位置,请根据你的实际情况自行修改。

生成数据库

有了实体模型,接下来可以生成数据库了。

使用迁移

生成数据库的第一种方法是使用迁移(Migration)。两行命令搞定。

在 VS2017 菜单中选择【工具】——>【NuGet 包管理器】——>【程序包管理器控制台】以打开程序包管理器控制台窗口,在此窗口输入如下命令:

Add-Migration InitialCreate

执行完毕后输入第二个命令:

Update-Database

如下图所示: 在解决方案管理器中,我们可以看到生成了一个新文件夹【Migrations】,这里自动生成了创建数据库的相关代码。

在 VS2017 菜单中选择【视图】——>【SQL Server 对象资源管理器】,打开 SQL Server 对象资源管理器。可以看到【StuinfoDB】数据库已经创建了,如下图所示: 很爽!两个命令就把数据库生成了,不过还可以使用另一种更干净的方法生成数据库。

在解决方案资源管理器中删除刚才生成的【Migrations】文件夹,并在 SQL Server 对象资源管理器中删除刚生成的【StuinfoDB】数据库。然后再接着往下做。

使用代码

还可以通过代码直接生成数据库,相对来说,这种方法更为方便,简洁。

打开【Program.cs】文件,更改Main方法如下:

public static void Main(string[] args)
{
    var host = BuildWebHost(args);
    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<StuinfoDbContext>();
            context.Database.EnsureCreated();
        }
        catch (Exception e)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(e, "初始化系统数据时出错!");
        }
    }
    host.Run();
}

运行程序,运行成功后,网页内容不必关心,直接关掉即可,再停掉程序运行。接下来打开 SQL Server 对象资源管理器,可以看到,再次生成了【StuinfoDB】数据库,而且没有生成【Migrations】文件夹。另外,这次生成的表和上次稍有不同,少了一个【dbo.__EFMigrationsHistory】。 context.Database.EnsureCreated();方法会自动生成数据库。

生成初始数据

之前在讲 PostgreSQL 时,我们使用 SQL 语句插入了一些初始数据以方便对程序进行测试。这里,也可以通过代码的方式向数据库插入一些同样的数据。那么,我们就专门创建一个类来做这个工作。

在【EntityFramework】文件夹下新建一个类,命名为 DbInitializer。输入如下代码:

using System;
using System.Linq;
using StuInfoSys.Models;

namespace StuInfoSys.EntityFramework
{
    public class DbInitializer
    {
        public static void Initialize(StuinfoDbContext context)
        {
            context.Database.EnsureCreated();
            if (context.Depments.Any())
            {   //如果已经存在数据,则返回
                return;
            }
            //向系部表插入数据
            var depments = new Depment[]
            {
                new Depment{DepName="机械系"},
                new Depment{DepName="电气系"},
                new Depment{DepName="计算机系"},
                new Depment{DepName="工商系"}
            };
            foreach (Depment d in depments)
            {
                context.Depments.Add(d);
            }
            context.SaveChanges(); //将数据提交到数据库

            //向班级表插入数据
            var classes = new Class[]
            {
                new Class{ClassName="自动化1701",DepId=2},
                new Class{ClassName="数据控1701",DepId=1},
                new Class{ClassName="物联网1701",DepId=3},
                new Class{ClassName="物联网1702",DepId=3},
                new Class{ClassName="软件1701",DepId=3}
            };
            foreach (Class c in classes)
            {
                context.Classes.Add(c);
            }
            context.SaveChanges();

            //向学生表插入数据
            var students = new Student[]
            {
                new Student{StuName="张三",Sex="男",Birthday=DateTime.Parse("1995-6-1"),ClassId=3},
                new Student{StuName="李四",Sex="男",Birthday=DateTime.Parse("1995-7-1"),ClassId=3},
                new Student{StuName="王五",Sex="女",Birthday=DateTime.Parse("1995-8-1"),ClassId=3},
                new Student{StuName="马六",Sex="男",Birthday=DateTime.Parse("1995-8-2"),ClassId=3},
                new Student{StuName="钱七",Sex="女",Birthday=DateTime.Parse("1995-8-3"),ClassId=3},
                new Student{StuName="刘八",Sex="男",Birthday=DateTime.Parse("1995-8-4"),ClassId=3},
                new Student{StuName="何九",Sex="女",Birthday=DateTime.Parse("1995-8-5"),ClassId=3},
                new Student{StuName="吕十",Sex="女",Birthday=DateTime.Parse("1995-8-5"),ClassId=3}
            };
            foreach(Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();
        }
    }
}

我们专门创建了一个DbInitializer类,它的静态方法Initialize专门用于对数据库进行初始化。请注意之前的context.Database.EnsureCreated();这句代码已经移到这个类里实现了。

接下来,还要对【Program.cs】文件里的代码稍作修改,请更改Main方法如下:

var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<StuinfoDbContext>();
        DbInitializer.Initialize(context); //修改此句
    }
    catch (Exception e)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(e, "初始化系统数据时出错!");
    }
}
host.Run();

删除之前生成的数据库,然后运行程序,打开 SQL Server 对象资源管理器,可以发现再次生成了数据库,并且数据表中已存在数据。

使用 PostgreSQL

之前我们使用的是 SQL Server 的本地数据库版本,主要是不需要引入任何开发包,学习起来非常方便,适合入门。那么我们也已经学习了 PostgreSQL,之前的这些操作是否对 PostgreSQL 有效呢?答案是肯定的,只需做很少的改变,之前的所有操作就可以应用到 PostgreSQL 数据库了。

在进行操作之前,首先按照《使用 pgadmin 操作 PostgreSQL》这篇文章所述的方法,如果已经创建了StuInfo数据库,则将它删除后重建,不添加任何数据表。

引入NuGet包

接下来更改程序,首先引入 PostgreSQL 驱动包。在 VS2017 菜单中选择【工具】——>【NuGet包管理器】——>【管理解决方案的 NuGet 程序包】,打开 NuGet - 解决方案 窗口。在其中选中【浏览】选项卡,在搜索栏中输入 npgsql。如下图所示: 找到 Npgsql.EntityFrameworkCore.PostgreSQL 包并选中。然后在右边窗口中选中项目 StuInfoSys ,接下来单击安装按钮,如下图所示: 安装完成后,打开解决方案资源管理器,查看依赖项文件夹,如果看到Npgsql.EntityFrameworkCore.PostgreSQL,说明已经安装成功:

更改数据库连接字符串

打开【appsettings.json】文件,更改ConnectionStrings项如下:

"ConnectionStrings": {
    "Default": "Host=167.107.19.163; Port=5432; Database=StuInfo; Username=postgres; Password=123456;"
  },

注意Host选项的 IP 地址按实际情况填写。

更改注入的服务

打开【Startup.cs】文件,更改ConfigureServices方法中的代码以使用 PostgreSQL 所需要的服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddEntityFrameworkNpgsql().
        AddDbContext<StuinfoDbContext>(d => d.UseNpgsql(Configuration.
        GetConnectionString("Default")));
    services.AddMvc();
}

运行程序(如果报错说 PostgreSQL 存在一个 session 需要关闭,最快的方法就是重启数据库,其它方法都很麻烦),然后关闭,再打开 pgadmin 查看数据库,发现新三张表已经创建,数据也已经插入数据表中。

经过以上操作,我们可以感受到,使用 EF Core 时,切换数据库实在太方便了,基本不需要付出什么代码。当然,并非支持所有数据库,但主流数据库如 Oracle 和 MySQL还是支持的。

;

© 2018 - IOT小分队文章发布系统 v0.3